//订阅 发布
class Dep {
    constructor() {
        this.subs = []; //存放所有的观察者
    }
    //订阅
    addSub(watcher) {
        this.subs.push(watcher);
    }
    //发布
    notify() {
        this.subs.forEach(watcher => watcher.update())
    }
}


//观察者
class Watcher {
    constructor(vm, expr, callback) {
        this.vm = vm;
        this.expr = expr;
        this.callback = callback;
        //默认存放一个旧值
        this.oldValue = this.get()
    }

    get() {
        //将定义的观察者挂载到 订阅/发布器 上
        Dep.target = this;
        //获取属性值
        let value = CompileUtil.getVal(this.vm, this.expr);
        //将这个观察者从 订阅/发布器 上卸载
        Dep.target = null;
        return value;
    }

    //更新操作，数据变化后 调用观察者的update方法
    update() {
        let newVal = CompileUtil.getVal(this.vm, this.expr)
        if (this.oldValue !== newVal) {
            this.callback(newVal)
        }
    }
}

//编译器
class Compiler {
    //el: 绑定的模板 vm: view model
    constructor(el, vm) {
        //判断模板传过来的是 字符串 还是 dom元素
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        //将当前页面模板中的元素 放到内存中
        let fragment = this.node2fragment(this.el)

        //将内存中的节点内容进行替换
        this.vm = vm
        //调用核心编译器
        this.compiler(fragment)

        //将内存中替换好内容元素 再塞回页面模板中
        this.el.appendChild(fragment)
    }

    //判断是否为dom元素函数
    isElementNode(node) {
        //nodeType: 1：元素节点  2：属性节点 3：文本节点
        return node.nodeType === 1;
    }

    //判断属性是否是v-开头的
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }

    //将node节点及其内部所有元素都放入内存中
    node2fragment(node) {
        //创建一个文档片段
        let fragment = document.createDocumentFragment(node)
        //将模板内的所有元素放入到文档片段中
        let firstChild;
        while (firstChild = node.firstChild) {
            //appendChild具有移除性 直接移动本体，添加到其他地方
            fragment.appendChild(firstChild)
        }
        //返回存有模板的文档片段
        return fragment;
    }

    //核心编译器
    compiler(node) {
        //获取文档中所有的子节点
        let childNodes = node.childNodes;
        //解构后包装成真数组
        [...childNodes].forEach(child => {
            //判断是否为元素节点
            if (this.isElementNode(child)) {
                //是 调用元素节点编译器
                this.compileElement(child)
                //如果是元素节点，则需要递归查找其子节点
                this.compiler(child)
            } else {
                //否 调用文本节点编译器
                this.compileText(child)
            }
        })
    }

    //元素节点编译器（找到所有 v- 开头的属性）
    compileElement(node) {
        //获取到元素上所有的属性
        let attributes = node.attributes;
        [...attributes].forEach(attr => {
            //attr v-model="school.name"
            let {
                name,
                value: expr
            } = attr;
            //判断是否是v-开头的属性
            if (this.isDirective(name)) {
                let [, directive] = name.split('-');
                //分割类似  on:click
                let [directiveName, eventName] = directive.split(':')
                //调用相应的指令处理器来处理
                CompileUtil[directiveName](node, expr, this.vm, eventName)
            }
        })
    }

    //文本节点编译器（找到所有文本节点中包含{{***}}的内容）
    compileText(node) {
        let content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            CompileUtil['text'](node, content, this.vm)
        }
    }


}

//编译工具箱
CompileUtil = {
    getVal(vm, expr) {
        return expr.split('.').reduce((data, current) => {
            return data[current]
        }, vm.$data)
    },
    setVal(vm, expr, val) {
        expr.split('.').reduce((data, current, index, arr) => {
            if (index === arr.length - 1) {
                data[current] = val
            }
            return data[current]
        }, vm.$data)
    },
    //v-model
    model(node, expr, vm) { //node：对应节点  expr：表达式（内容）  vm：当前实例
        //给输入框赋予 value 属性  node.value = xxx
        let fn = this.updater['modelUpdater'];
        //给输入框添加一个观察者
        new Watcher(vm, expr, (newVal) => {
            fn(node, newVal); //当发现值变化，则重新给输入框赋值 更新页面
        })
        //添加事件，监听输入框内容变化
        node.addEventListener('input', (e) => {
            let newValue = e.target.value;
            //将输入框中的值 传给el.$data中的对应属性
            this.setVal(vm, expr, newValue)
        })
        let value = this.getVal(vm, expr);
        fn(node, value); //更新页面
    },
    //v-on
    on(node, expr, vm, eventName){
        node.addEventListener(eventName,function(e){
            vm[expr].call(vm, e)
        })
    },
    html(node, expr, vm) {
        console.log(node);
        console.log(expr);
        console.log(vm);
        let fn = this.updater['htmlUpdater']

        new Watcher(vm, expr, (newValue) => {
            fn(node, newValue)
        })

        let value = this.getVal(vm, expr)
        fn(node, value)

        
    },
    getContentVal(vm, expr) { //获取{{xxx}}{{xx}}内部的值，并根据值返回替换完的完整文本
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(vm, args[1])
        })

    },
    //{{xxx}}
    text(node, expr, vm) {
        //处理文本节点
        let fn = this.updater['textUpdater']
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            //给文本节点中每一个{{xxx}}中的值添加一个观察者
            new Watcher(vm, args[1], () => {
                fn(node, this.getContentVal(vm, expr)) //更新页面
            })
            return this.getVal(vm, args[1])
        })
        fn(node, content) //更新页面
    },
    updater: {
        //更新表单节点数据
        modelUpdater(node, value) {
            node.value = value
        },
        //更新html
        htmlUpdater(node, value) {
            node.innerHTML = value
        },
        //更新文本节点数据
        textUpdater(node, value) {
            node.textContent = value
        }
    }
}

//实现数据劫持
class Observer {
    constructor(data) {
        this.observer(data)
    }

    observer(data) {
        if (data && typeof data == 'object') {
            for (let key in data) {
                this.defineReactive(data, key, data[key])
            }
        }
    }

    defineReactive(data, key, value) {
        //如果对象内还是对象，则递归劫持
        this.observer(value)
        //给每个属性添加 发布/订阅 功能
        let dep = new Dep()
        Object.defineProperty(data, key, {
            get() {
                //当定义观察者时 Dep.target 即为正在生成的观察者
                //定义观察者时，会调用观察者及其父对象的get方法  给这个属性及其父对象添加对该属性观察者的订阅
                Dep.target && dep.addSub(Dep.target)

                return value
            },
            set: (newVal) => {
                if (newVal != value) {
                    //劫持修改后的属性
                    this.observer(newVal)
                    value = newVal
                    //唤醒 发布/订阅器 的更新功能
                    dep.notify()
                }
            }
        })
    }
}

//基类
class MyVue {
    constructor(options) {
        this.$el = options.el
        this.$data = options.data
        let computed = options.computed;
        let methods = options.methods
        //如果这个根组件存在 则编译模板
        if (this.$el) {

            //将所有的数据 全部转换成用 Object.defineProperty 来定义
            new Observer(this.$data)

            //将数据获取操作 vm对象上的取值，都代理到 vm.$data 上
            this.proxtVm(this.$data)

            //将计算属性中的数据挂载到 this.$data 中
            for (let key in computed) {
                Object.defineProperty(this.$data, key, {
                    get: () => {
                        return computed[key].call(this)
                    }
                })
            }

            //将函数挂载到 vm 中 供获取
            for(let key in methods){
                Object.defineProperty(this, key, {
                    get: ()=>{
                        return methods[key]
                    }
                })
            }

            new Compiler(this.$el, this)
        }
    }

    //代理数据获取 vm.$data.name  == vm.name
    proxtVm(data) {
        for (let key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return this.$data[key]
                },
                set(newVal){
                    data[key] = newVal
                }
            })
        }
    }

}